# SequenceStoredAsTableSelector.java

    package org.codefilarete.stalactite.mapping.id.sequence;

    import javax.annotation.Nullable;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.Map;

    import org.codefilarete.stalactite.sql.ConnectionProvider;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Database.Schema;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.statement.DMLGenerator;
    import org.codefilarete.stalactite.sql.statement.PreparedSQL;
    import org.codefilarete.stalactite.sql.statement.ReadOperation;
    import org.codefilarete.stalactite.sql.statement.ReadOperationFactory;
    import org.codefilarete.stalactite.sql.statement.WriteOperation;
    import org.codefilarete.stalactite.sql.statement.WriteOperationFactory;
    import org.codefilarete.stalactite.sql.statement.binder.DefaultParameterBinders;
    import org.codefilarete.stalactite.sql.statement.binder.ParameterBinder;
    import org.codefilarete.tool.collection.Maps;

    import static java.util.Collections.emptyList;
    import static java.util.Collections.emptyMap;
    import static org.codefilarete.tool.collection.Arrays.asList;

    /**
     * Reader for a table that acts as a sequence.
     * The table contains only one line, which is updated when the sequence is asked for its next value.
     * The updates follow the sequence increment size.
     *
     * @author Guillaume Mary
     */
    public class SequenceStoredAsTableSelector implements org.codefilarete.tool.function.Sequence<Long> {

    	private final SequenceAsTable sequenceTable;
    	private final int initialValue;
    	private final int batchSize;
    	private final ConnectionProvider connectionProvider;

    	private final PreparedSQL selectSequenceStatement;
    	private final PreparedSQL insertSequenceStatement;
    	private final PreparedSQL updateSequenceStatement;
    	private final ReadOperationFactory readOperationFactory;
    	private final WriteOperationFactory writeOperationFactory;

    	/**
    	 * Normal constructor
    	 *
    	 * @param schema optional schema to store the table in
    	 * @param tableName name of the sequence, used for storing-table name
    	 * @param initialValue initial sequence value
    	 * @param batchSize sequence increment
    	 * @param dmlGenerator DML generator for select, update and insert statements
    	 * @param readOperationFactory select operation factory
    	 * @param writeOperationFactory update and insert operations factory
    	 * @param connectionProvider statements connection provider
    	 */
    	public SequenceStoredAsTableSelector(@Nullable Schema schema,
    										 String tableName,
    										 int initialValue,
    										 int batchSize,
    										 DMLGenerator dmlGenerator,
    										 ReadOperationFactory readOperationFactory,
    										 WriteOperationFactory writeOperationFactory,
    										 ConnectionProvider connectionProvider) {
    		this.readOperationFactory = readOperationFactory;
    		this.writeOperationFactory = writeOperationFactory;
    		this.sequenceTable = new SequenceAsTable(schema, tableName);
    		this.initialValue = initialValue;
    		this.batchSize = batchSize;
    		this.connectionProvider = connectionProvider;

    		this.selectSequenceStatement = new PreparedSQL(dmlGenerator
    				.buildSelect(sequenceTable, asList(sequenceTable.nextVal), emptyList()).getSQL(), emptyMap());
    		Map<Integer, ParameterBinder<Long>> writeBinders = Maps.asMap(1, DefaultParameterBinders.LONG_BINDER);
    		this.insertSequenceStatement = new PreparedSQL(dmlGenerator
    				.buildInsert(asList(sequenceTable.nextVal)).getSQL(), writeBinders);
    		this.updateSequenceStatement = new PreparedSQL(dmlGenerator
    				.buildUpdate(asList(sequenceTable.nextVal), emptyList()).getSQL(), writeBinders);
    	}

    	public Table getSequenceTable() {
    		return sequenceTable;
    	}

    	/**
    	 * Gives current sequence value, based on the lonely line stored in the table.
    	 * Write the new value after reading (computed with increment value).
    	 * @return current sequence value
    	 */
    	@Override
    	public synchronized Long next() {
    		long result;
    		boolean hasData;
    		try (ReadOperation<Integer> readOperation = readOperationFactory.createInstance(selectSequenceStatement, connectionProvider, 1)) {
    			ResultSet resultSet = readOperation.execute();
    			hasData = resultSet.next();
    			if (hasData) {
    				result = resultSet.getLong(1);
    			} else {
    				result = initialValue;
    			}
    		} catch (SQLException e) {
    			throw new RuntimeException(e);
    		}
    		WriteOperation<Integer> writeOperation;
    		long valueToWrite = result + batchSize;
    		if (hasData) {
    			writeOperation = writeOperationFactory.createInstance(updateSequenceStatement, connectionProvider);
    		} else {
    			writeOperation = writeOperationFactory.createInstance(insertSequenceStatement, connectionProvider);
    		}
    		try (WriteOperation<Integer> ignored = writeOperation) {
    			writeOperation.setValue(1, valueToWrite);
    			writeOperation.execute();
    		}
    		return result;
    	}

    	/**
    	 * Particular {@link Table} which stores sequence value
    	 * @author Guillaume Mary
    	 */
    	private static class SequenceAsTable extends Table<SequenceAsTable> {

    		private final Column<SequenceAsTable, Long> nextVal = addColumn("nextVal", long.class).primaryKey();

    		public SequenceAsTable(@Nullable Schema schema, String name) {
    			super(schema, name);
    		}
    	}
    }